package com.silence.gmall.aspect;

import com.silence.gmall.annotation.MallCache;
import com.silence.gmall.component.CatchOpsComponent;
import com.silence.gmall.util.AopAndElUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Type;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * @Author silence
 * @Date 2025-10-26 03:08
 * @Email 792245022@qq.com
 * @Version 1.0
 * @Description 缓存切面
 */
@Slf4j
@Aspect
@Order(10)
@Component
@RequiredArgsConstructor
public class MallCacheAspect {

    /**
     * 注入缓存操作组件 CatchOpsComponent
     */
    private final CatchOpsComponent catchOpsComponent;

    /**
     * 注入 RedissonClient
     */
    private final RedissonClient redissonClient;

    /**
     * 定义切点
     */
    @Pointcut("@annotation(com.silence.gmall.annotation.MallCache)")
    public void mallCachePointCut() {

    }

    /**
     * 环绕通知
     *
     * @param joinPoint 切点
     * @return 返回值
     * @throws Throwable 异常
     */
    @Around("mallCachePointCut()")
    public Object mallCacheAround(ProceedingJoinPoint joinPoint) throws Throwable {
        // 返回结果
        Object result;

        // 锁的状态
        boolean lockStatus = false;

        // 获取注解
        MallCache mallCache = AopAndElUtil.getMethodAnnotation(joinPoint, MallCache.class);
        assert mallCache != null;

        // 获取分布式锁键
        RLock lock = null;

        // 获取缓存键
        String cacheKey = mallCache.cacheKey();
        // 获取分布式锁键
        String lockKey = mallCache.lockKey();
        // 获取布隆过滤器键
        String bloomKey = mallCache.bloomKey();
        // 获取布隆过滤器值
        String bloomValue = mallCache.bloomValue();
        // 获取缓存过期时间
        long timeout = mallCache.timeout();
        // 获取缓存过期时间单位
        TimeUnit unit = mallCache.unit();

        try {
            // 构造表单时上下文
            EvaluationContext context = AopAndElUtil.buildContext(joinPoint);
            // 获取缓存键的值
            String cacheKeyExValue = AopAndElUtil.getExpressionValueByKey(cacheKey, context, String.class);
            log.info("【缓存切面】 - 缓存键为：{}", cacheKeyExValue);

            // 获取方法返回值类型
            Type type = AopAndElUtil.getMethodGenericReturnType(joinPoint);
            // 获取缓存数据
            Optional<Object> catchData = catchOpsComponent.getCatchData(cacheKeyExValue, type);
            log.info("【缓存切面】 - 缓存数据为：{}", catchData);

            // 如果缓存数据存在，直接返回
            if (catchData.isPresent()) {
                return catchData.get();
            }

            // 如果需要布隆过滤器，则使用布隆过滤器
            if (StringUtils.isNotBlank(bloomKey)) {
                // 获取布隆过滤器
                RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter(bloomKey);
                // 获取布隆过滤器值
                Object bloomValueExValue = AopAndElUtil.getExpressionValueByKey(bloomValue, context, Object.class);

                // 校验 redis bitMap 布隆过滤器
                boolean isExist = bloomFilter.contains(bloomValueExValue);
                log.info("【缓存切面】 - 布隆过滤器值：{}，是否存在：{}", bloomValueExValue, isExist);

                // 如果不存在，直接返回null
                if (!isExist) {
                    return null;
                }
            }

            // 如果不需要分布式锁，则直接返回值
            if (StringUtils.isBlank(lockKey)) {
                // 调用实际方法
                result = joinPoint.proceed();
                // 保存缓存数据
                catchOpsComponent.saveData(cacheKeyExValue, result, timeout, unit);
                // 返回结果
                return result;
            }

            // 获取分布式锁
            String lockKeyExValue = AopAndElUtil.getExpressionValueByKey(lockKey, context, String.class);
            lock = redissonClient.getLock(lockKeyExValue);

            // 尝试获取一下锁
            boolean ifLock = lock.tryLock();

            if (!ifLock) {
                try {
                    // 如果没有获取到锁，则休眠500毫秒后重试
                    TimeUnit.MILLISECONDS.sleep(500);

                    // 递归调用 getSkuDetail 方法，直到获取到锁为止
                    return catchOpsComponent.getCatchData(cacheKeyExValue, type)
                            .orElse(null);
                } catch (InterruptedException _) {
                    Thread.currentThread().interrupt();
                    return null;
                }
            }

            // 加锁状态为成功
            lockStatus = true;
            // 调用实际方法
            result = joinPoint.proceed();
            // 保存缓存数据
            catchOpsComponent.saveData(cacheKeyExValue, result, timeout, unit);
            // 返回结果
            return result;
        } finally {
            if (Objects.nonNull(lock) && lockStatus
                    && lock.isHeldByCurrentThread()) {
                // 开启了锁功能，并且加了锁，才需要解锁
                lock.unlock();
            }
        }
    }
}
